package com.example.linphone_flutter;


import android.Manifest;
import android.app.Activity;

import android.content.Context;
import android.content.pm.PackageManager;

import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;

import org.linphone.core.Account;
import org.linphone.core.AccountParams;
import org.linphone.core.Address;
import org.linphone.core.AudioDevice;
import org.linphone.core.AuthInfo;
import org.linphone.core.Call;
import org.linphone.core.CallParams;
import org.linphone.core.Core;
import org.linphone.core.Factory;
import org.linphone.core.MediaEncryption;
import org.linphone.core.TransportType;
import org.linphone.core.tools.Log;

import java.util.ArrayList;
import java.util.List;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

/**
 * LinphoneFlutterPlugin
 */
public class LinphoneFlutterPlugin implements FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private MethodChannel channel;
    private Core core;
    private Factory factory;
    private LinphoneListener coreListener;
    private Context context;
    private ChatMessageManager chatMessageManager;
//    private  CallViewModel callViewModel;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        context = flutterPluginBinding.getApplicationContext();
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "linphone_flutter");
        channel.setMethodCallHandler(this);
        factory = Factory.instance();
        factory.setDebugMode(false, "");
        core = factory.createCore(null, null, context);
        core.setPushNotificationEnabled(true);
        coreListener = new LinphoneListener(flutterPluginBinding.getBinaryMessenger());
        chatMessageManager = new ChatMessageManager(core, flutterPluginBinding.getBinaryMessenger());
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals("getPlatformVersion")) {
            result.success("Android " + android.os.Build.VERSION.RELEASE);
        } else if (call.method.equals("getLinphoneVersion")) {
            result.success(core.getVersion());
        } else if (call.method.equals("login")) {
            login(call, result);
        } else if (call.method.equals("unregister")) {
            unregister(call, result);
        } else if (call.method.equals("delete")) {
            delete(call, result);
        } else if (call.method.equals("terminate")) {
            terminate(call, result);
        } else if (call.method.equals("accept")) {
            accept(call, result);
        } else if (call.method.equals("outgoingCall")) {
            outgoingCall(call, result);
        } else if (call.method.equals("hangUp")) {
            hangUp(call, result);
        } else if (call.method.equals("transferTo")) {
            transferTo(call, result);
        } else if (call.method.equals("pauseOrResume")) {
            pauseOrResume(call, result);
        } else if (call.method.equals("micEnabled")) {
            micEnabled(call, result);
        } else if (call.method.equals("toggleSpeaker")) {
            toggleSpeaker(call, result);
        } else if (call.method.equals("remoteAddressUri")) {
            remoteAddressUri(call, result);
        } else if (call.method.equals("requestPermissions")) {
            requestPermissions(call, result);
        } else if (call.method.equals("sendMessage")) {
            chatMessageManager.sendMessage(call, result);
        } else {
            result.notImplemented();
        }
    }

    void login(MethodCall call, Result result) {
        String username = call.argument("username");
        String password = call.argument("password");
        String domain = call.argument("domain");
        AuthInfo authInfo = Factory.instance().createAuthInfo(username, null, password, null, null, domain, null);
        AccountParams accountParams = core.createAccountParams();

        // A SIP account is identified by an identity address that we can construct from the username and domain
        Address identity = Factory.instance().createAddress(String.format("sip:%s@%s", username, domain));
        accountParams.setIdentityAddress(identity);

        // We also need to configure where the proxy server is located
        Address address = Factory.instance().createAddress(String.format("sip:%s", domain));
        // We use the Address object to easily set the transport protocol
        address.setTransport(TransportType.Tcp);
        accountParams.setServerAddress(address);
        // And we ensure the account will start the registration process
        accountParams.setRegisterEnabled(true);
        accountParams.setPushNotificationAllowed(true);
        // Now that our AccountParams is configured, we can create the Account object
        Account account = core.createAccount(accountParams);
        // Now let's add our objects to the Core
        core.addAuthInfo(authInfo);
        core.addAccount(account);

        // Also set the newly added account as default
        core.setDefaultAccount(account);
        core.addListener(coreListener);

        // Finally we need the Core to be started for the registration to happen (it could have been started before)
        core.start();

        if (!core.isPushNotificationAvailable()) {
            Log.e("Something is wrong with the push setup!");
        }

        result.success(null);
    }

    void unregister(MethodCall call, Result result) {
        Account account = core.getDefaultAccount();
        if (account != null) {
            // Returned params object is const, so to make changes we first need to clone it
            AccountParams params = account.getParams();
            AccountParams clonedParams = params.clone();
            // Now let's make our changes
            clonedParams.setRegisterEnabled(false);
            // And apply them
            account.setParams(clonedParams);
        }
        result.success(null);
    }

    void delete(MethodCall call, Result result) {
        // To completely remove an Account
        Account account = core.getDefaultAccount();
        if (account != null) {
            core.removeAccount(account);
        }
//        coreListener.delete();
        // To remove all accounts use
        core.clearAccounts();
        // Same for auth info;
        core.clearAllAuthInfo();
        chatMessageManager.destroy();
        result.success(null);
    }

    //拒接
    void terminate(MethodCall call, Result result) {
        core.getCurrentCall().terminate();
        result.success(null);
    }

    //接听
    void accept(MethodCall call, Result result) {
        core.getCurrentCall().accept();
        result.success(null);
    }

    //打出
    void outgoingCall(MethodCall call, Result result) {
        String remoteSipUri = call.argument("remoteSipUri");
        Address remoteAddress = Factory.instance().createAddress(remoteSipUri);
        if (remoteAddress == null)
            return;

        CallParams params = core.createCallParams(null);
        if (params == null)
            return;

        params.setMediaEncryption(MediaEncryption.None);
        core.inviteAddressWithParams(remoteAddress, params);
        result.success(null);
    }

    //挂断
    void hangUp(MethodCall call, Result result) {
        Call currentCall = getCurrentCall();
        if (currentCall == null) {
            return;
        }
        currentCall.terminate();
        result.success(null);
    }

    void transferTo(MethodCall call, Result result) {
        String remoteSipUri = call.argument("remoteSipUri");
        Address remoteAddress = Factory.instance().createAddress(remoteSipUri);
        if (remoteAddress == null)
            return;

        CallParams params = core.createCallParams(null);
        if (params == null)
            return;

        params.setMediaEncryption(MediaEncryption.None);
        core.getCurrentCall().transferTo(remoteAddress);
        result.success(null);
    }

    void pauseOrResume(MethodCall call, Result result) {
        Call currentCall = getCurrentCall();
        if (currentCall == null) {
            return;
        }
        if (currentCall.getState() != Call.State.Paused && currentCall.getState() != Call.State.Pausing) {
            currentCall.pause();
        } else if (currentCall.getState() != Call.State.Resuming) {
            currentCall.resume();
        }
        result.success(null);
    }


    void micEnabled(MethodCall call, Result result) {
        core.setMicEnabled(!core.isMicEnabled());
        result.success(core.isMicEnabled());
    }

    void toggleSpeaker(MethodCall call, Result result) {
        AudioDevice currentAudioDevice = core.getCurrentCall().getOutputAudioDevice();
        boolean speakerEnabled = currentAudioDevice.getType() == AudioDevice.Type.Speaker;
        for (AudioDevice audioDevice : core.getAudioDevices()) {
            if (speakerEnabled && audioDevice.getType() == AudioDevice.Type.Earpiece) {
                core.getCurrentCall().setOutputAudioDevice(audioDevice);
                result.success(false);
                return;
            } else if (!speakerEnabled && audioDevice.getType() == AudioDevice.Type.Speaker) {
                core.getCurrentCall().setOutputAudioDevice(audioDevice);
                result.success(true);
                return;
            }
        }
    }

    void toggleVideo(MethodCall call, Result result) {

    }

    void toggleCamera(MethodCall call, Result result) {
    }

    void remoteAddressUri(MethodCall call, Result result) {
        String uri = core.getCurrentCall().getRemoteAddress().asStringUriOnly();
        result.success(uri);
    }

    void requestPermissions(MethodCall call, Result result) {
        try {
            String[] permissionArrays = new String[]{
                    Manifest.permission.CAMERA,
                    Manifest.permission.USE_SIP,
                    Manifest.permission.RECORD_AUDIO,
                    Manifest.permission.ACCESS_NETWORK_STATE,
                    Manifest.permission.CHANGE_NETWORK_STATE,
                    Manifest.permission.ACCESS_WIFI_STATE,
                    Manifest.permission.CHANGE_WIFI_STATE,
            };
            boolean isSuccess = checkPermissions(permissionArrays);
            if (isSuccess) {
                result.success(true);
            } else {
                result.error("Permission Error", "Permission is not granted.", "Error");
            }
        } catch (Exception e) {
            e.printStackTrace();
            result.error(null, e.toString(), null);
        }
    }

    public boolean checkPermissions(String[] permissions) {
        try {
            int result;
            List<String> listPermissionsNeeded = new ArrayList<>();
            for (String p : permissions) {
                result = ContextCompat.checkSelfPermission((Activity) context, p);
                if (result != PackageManager.PERMISSION_GRANTED) {
                    listPermissionsNeeded.add(p);
                }
            }
            if (!listPermissionsNeeded.isEmpty()) {
                ActivityCompat.requestPermissions((Activity) context, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]), 1111);
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }


    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
    }

    private Call getCurrentCall() {
        if (core.getCallsNb() == 0) {
            return null;
        }
        Call phoneCall = core.getCurrentCall() != null ?
                core.getCurrentCall() : core.getCalls()[0];
        if (phoneCall == null) {
            return null;
        }
        return phoneCall;
    }
}
